package bma.common.netty.webserver.common;

import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.Lock;

import org.jboss.netty.handler.codec.http.Cookie;
import org.jboss.netty.handler.codec.http.CookieEncoder;
import org.jboss.netty.handler.codec.http.DefaultCookie;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;

import bma.common.langutil.core.DateTimeUtil;
import bma.common.langutil.core.ExceptionUtil;
import bma.common.langutil.core.LruCache;
import bma.common.langutil.core.LruCacheEvictListener;
import bma.common.langutil.core.StringUtil;
import bma.common.langutil.core.ValueUtil;
import bma.common.netty.webserver.Session;
import bma.common.netty.webserver.SessionFactory;
import bma.common.netty.webserver.SessionManager;
import bma.common.netty.webserver.WebServerUtil;

public abstract class SessionManagerAbstract implements SessionManager {

	final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(SessionManagerAbstract.class);

	protected String cookieKey = "_NWS_SESSIONID";
	protected int cookieTimeout; // seconds
	protected String cookiePath;
	protected String cookieDomain;

	protected String sessionPrefix;
	protected String sessionSignKey = "";
	protected int sessionDataSize = 128;
	protected int sessionTimeout = 60 * 1000;
	protected boolean threadLocal = true;

	protected SessionFactory factory;

	public int getSessionTimeout() {
		return sessionTimeout;
	}

	public void setSessionTimeout(int sessionDataTimeout) {
		this.sessionTimeout = sessionDataTimeout;
	}

	public void setSessionTimeoutValue(String p) {
		try {
			this.sessionTimeout = (int) DateTimeUtil.parsePeriodValue(p,
					sessionTimeout);
		} catch (Exception e) {
			ExceptionUtil.throwRuntime(e);
		}
	}

	public String getCookieKey() {
		return cookieKey;
	}

	public void setCookieKey(String cookieKey) {
		this.cookieKey = cookieKey;
	}

	public int getCookieTimeout() {
		return cookieTimeout;
	}

	public void setCookieTimeout(int cookieTimeout) {
		this.cookieTimeout = cookieTimeout;
	}

	public String getCookiePath() {
		return cookiePath;
	}

	public void setCookiePath(String cookiePath) {
		this.cookiePath = cookiePath;
	}

	public String getCookieDomain() {
		return cookieDomain;
	}

	public void setCookieDomain(String cookieDomain) {
		this.cookieDomain = cookieDomain;
	}

	public String getSessionPrefix() {
		return sessionPrefix;
	}

	public void setSessionPrefix(String sessionPrefix) {
		this.sessionPrefix = sessionPrefix;
	}

	public String getSessionSignKey() {
		return sessionSignKey;
	}

	public void setSessionSignKey(String sessionSignKey) {
		this.sessionSignKey = sessionSignKey;
	}

	public SessionFactory getFactory() {
		return factory;
	}

	public void setFactory(SessionFactory factory) {
		this.factory = factory;
	}

	public int getSessionDataSize() {
		return sessionDataSize;
	}

	public void setSessionDataSize(int sessionDataSize) {
		this.sessionDataSize = sessionDataSize;
	}

	public boolean isThreadLocal() {
		return threadLocal;
	}

	public void setThreadLocal(boolean locked) {
		this.threadLocal = locked;
	}

	public String createSessionId() {
		StringBuilder sb = new StringBuilder(128);
		if (this.sessionPrefix != null) {
			sb.append(this.sessionPrefix);
		}
		sb.append(UUID.randomUUID().toString().replaceAll("-", "o"));
		String v = sb.toString();
		sb.append('-');
		sb.append(StringUtil.md5((this.sessionSignKey + v).getBytes())
				.substring(16));
		return sb.toString();
	}

	public boolean isValid(String sid) {
		if (ValueUtil.empty(sid))
			return false;
		String[] l = sid.split("-", 2);
		if (l.length < 2)
			return false;
		String v = StringUtil.md5((this.sessionSignKey + l[0]).getBytes())
				.substring(16);
		if (StringUtil.equalsIgnoreCase(l[1], v)) {
			return true;
		} else {
			if (log.isDebugEnabled()) {
				log.debug("invalid sessionId - {}/{}", sid, v);
			}
			return false;
		}
	}

	public String getSessionId(HttpRequest request) {
		Set<Cookie> cookies = WebServerUtil.getCookies(request);
		for (Cookie ck : cookies) {
			if (ck.getName().equals(this.cookieKey)) {
				String sid = ck.getValue();
				if (isValid(sid))
					return sid;
				return null;
			}
		}
		return null;
	}

	public void setSessionId(HttpResponse response, String sessionId) {
		CookieEncoder cookieEncoder = new CookieEncoder(true);
		DefaultCookie cookie = new DefaultCookie(this.cookieKey, sessionId);
		if (ValueUtil.notEmpty(this.cookieDomain)) {
			cookie.setDomain(this.cookieDomain);
		}
		if (ValueUtil.notEmpty(this.cookiePath)) {
			cookie.setPath(this.cookiePath);
		}
		if (this.cookieTimeout > 0) {
			cookie.setMaxAge(this.cookieTimeout);
		}
		cookieEncoder.addCookie(cookie);
		response.setHeader(HttpHeaders.Names.SET_COOKIE, cookieEncoder.encode());
	}

	protected Session getSession(SessionCache data, String sid, boolean created) {
		Session s = data.get(sid);
		if (s == null) {
			if (created) {
				Session cs = this.factory.createSession(sid,
						this.sessionTimeout);
				s = data.putIfAbsent(sid, cs);
				if (s == null) {
					s = new SessionNew(cs);
				}
			}
		}
		return s;
	}

	protected void onSessionEvict(String sid, Session s) {
	}

	protected class SessionCache extends LruCache<String, Session> {

		public SessionCache(int initialCapacity, Lock lock) {
			super(initialCapacity,
					new LruCacheEvictListener<String, Session>() {
						@Override
						public void onEviction(String key, Session value) {
							onSessionEvict(key, value);
						}
					}, lock);
		}

		@Override
		protected boolean isEvictable(CacheItem<String, Session> item) {
			Session s = item.getValue();
			return isSessionValid(s);
		}
	}

	public static boolean isSessionValid(Session s) {
		if (s == null)
			return false;
		return System.currentTimeMillis() >= s.getLastAccessedTime()
				+ s.getMaxInactiveInterval();
	}

}
